//
// Created by yulr on 2019/3/29.
//

#include "cn_yulr_v4l2multicamera_V4L2Camera.h"

#include <android/log.h>
#include <android/bitmap.h>

#include <string.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include <fcntl.h>              /* low-level i/o */
#include <unistd.h>
#include <errno.h>
#include <malloc.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/ioctl.h>

#include <asm/types.h>          /* for videodev2.h */

#include <linux/videodev2.h>

#define  LOG_TAG    "V4L2Camera Native"
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#define CLEAR(x) memset (&(x), 0, sizeof (x))

struct buffer {
    void *start;
    size_t length;
};

/**
 * 最大摄像头个数
 */
#define CAMERANUM 2

static char dev_name[CAMERANUM][16];
static int fd[CAMERANUM] = {-1, -1};
struct buffer *buffers[CAMERANUM] = {NULL, NULL};
static unsigned int n_buffers[CAMERANUM] = {0, 0};

int getFdIndex(int tempFd) {
    for (int i = 0; i < CAMERANUM; i++) {
        if (fd[i] == tempFd) {
//            LOGE("getFdIndex Index:%d Value:%d", i, tempFd);
            return i;
        }
    }
    return -1;
}

int saveFd(int tempFd) {
    for (int i = 0; i < CAMERANUM; i++) {
        if (fd[i] == -1) {
            fd[i] = tempFd;
//            LOGE("SavedFd Index:%d Value:%d", i, tempFd);
            return i;
        }
    }
    return -1;
}

void saveFdPathInfo(int tempFd, const char *cameraPath) {
    int index = getFdIndex(tempFd);
    if (index != -1) {
        memcpy(dev_name[index], cameraPath, sizeof(dev_name[index]));
    }
}

int errnoexit(const char *s) {
    LOGE("%s error %d, %s", s, errno, strerror(errno));
    return ERROR;
}

JNIEXPORT jint JNICALL
Java_cn_yulr_v4l2multicamera_V4L2Camera_prepareCamera(JNIEnv *env, jobject thiz,
                                                      jstring videoPath, jint width, jint height,
                                                      jint pixelformat) {

    LOGE("prepareCamera");

    int returnValue = ERROR;
    char buf[16];
    CLEAR(buf);
    const char *str = env->GetStringUTFChars(videoPath, 0);
    memcpy(buf, str, sizeof(buf));

    int index = openDevice(buf);
    if (index != -1) {
        returnValue = initdevice(index, width, height, pixelformat);
        if (returnValue != ERROR) {
            returnValue = startcapturing(index);
            if (returnValue == ERROR) {
                uninitdevice(index);
                close(fd[index]);
            }
        } else {
            close(fd[index]);
        }
    }

    env->ReleaseStringUTFChars(videoPath, str);

    if (returnValue == ERROR)
        return ERROR;

    return fd[index];
}

int openDevice(char *videoPath) {

    LOGE("OpenDevice:%s", videoPath);
    struct stat st;
    CLEAR(st);
    if (-1 == stat(videoPath, &st)) {
        LOGE("Cannot identify '%s': %d, %s", videoPath, errno, strerror(errno));
        return ERROR;
    }

    if (!S_ISCHR (st.st_mode)) {
        LOGE("%s is no device", videoPath);
        return ERROR;
    }

    int tempFd = open(videoPath, O_RDWR | O_NONBLOCK, 0);

    if (-1 == tempFd) {
        LOGE("Cannot open '%s': %d, %s", videoPath, errno, strerror(errno));
        return ERROR;
    }

    int returnValue = saveFd(tempFd);
    saveFdPathInfo(tempFd, videoPath);

    return returnValue;

}

JNIEXPORT jint JNICALL
Java_cn_yulr_v4l2multicamera_V4L2Camera_processCamera(JNIEnv *env, jobject thiz, jint videoFd,
                                                      jbyteArray buffer, jint length) {
    int index = getFdIndex(videoFd);
    if (index == -1) {
        return ERROR;
    }
    jbyte *arr = env->GetByteArrayElements(buffer, 0);
    int readLength = readframeonce(index, (char *) arr, length);
    env->ReleaseByteArrayElements(buffer, arr, JNI_OK);
    return readLength;
}

JNIEXPORT jint JNICALL
Java_cn_yulr_v4l2multicamera_V4L2Camera_stopCamera(JNIEnv *env, jobject thiz, jint videoFd) {
    int index = getFdIndex(videoFd);
    if (index == -1 && fd[index] != -1) {
        LOGE("The VideoFd %d is not opened yet!", videoFd);
        return -1;
    }
    stopcapturing(index);
    uninitdevice(index);
    closedevice(index);
    fd[index] = -1;
    return 0;
}

/**
 * 查看指定的路径是否存在或者是否可用
 *
 * @param cameraPath 路径信息
 * @return -1 不可用；0 可用
 */
int checkCamerabase(char *cameraPath) {
    struct stat st;
    int returnValue = stat(cameraPath, &st);
    return returnValue;
}

int initdevice(int index, int width, int height, int pixelformat) {

    LOGE("initdevice");

    struct v4l2_capability cap;
    struct v4l2_cropcap cropcap;
    struct v4l2_crop crop;
    struct v4l2_format fmt;
    unsigned int min;

    CLEAR(cap);
    CLEAR(cropcap);
    CLEAR(crop);
    CLEAR(fmt);

    if (-1 == xioctl(fd[index], VIDIOC_QUERYCAP, &cap)) {
        if (EINVAL == errno) {
            LOGE("%d is no V4L2 device", fd[index]);
            return ERROR;
        } else {
            return errnoexit("VIDIOC_QUERYCAP");
        }
    }

    if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
        LOGE("%s is no video capture device", dev_name[index]);
        return ERROR;
    }


    if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
        LOGE("%s does not support streaming i/o", dev_name[index]);
        return ERROR;
    }

    CLEAR(cropcap);

    cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    if (0 == xioctl(fd[index], VIDIOC_CROPCAP, &cropcap)) {
        crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        crop.c = cropcap.defrect;

        if (-1 == xioctl(fd[index], VIDIOC_S_CROP, &crop)) {
            switch (errno) {
                case EINVAL:
                    break;
                default:
                    break;
            }
        }
    } else {
    }

    CLEAR(fmt);

    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = (__u32) width;
    fmt.fmt.pix.height = (__u32) height;
    fmt.fmt.pix.pixelformat = (__u32) pixelformat;
//    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV21;
    fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;

    if (-1 == xioctl(fd[index], VIDIOC_S_FMT, &fmt))
        return errnoexit("VIDIOC_S_FMT");

    min = fmt.fmt.pix.width * 2;
    if (fmt.fmt.pix.bytesperline < min)
        fmt.fmt.pix.bytesperline = min;
    min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height;
    if (fmt.fmt.pix.sizeimage < min)
        fmt.fmt.pix.sizeimage = min;

    return initmmap(index);

}

int initmmap(int index) {
    struct v4l2_requestbuffers req;

    CLEAR (req);

    req.count = 4;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;

    if (-1 == xioctl(fd[index], VIDIOC_REQBUFS, &req)) {
        if (EINVAL == errno) {
            LOGE("%s does not support memory mapping", dev_name[index]);
            return ERROR;
        } else {
            return errnoexit("VIDIOC_REQBUFS");
        }
    }

    if (req.count < 2) {
        LOGE("Insufficient buffer memory on %s", dev_name[index]);
        return ERROR;
    }

    buffers[index] = (buffer *) calloc(req.count, sizeof(struct buffer));

    if (!buffers[index]) {
        LOGE("Out of memory");
        return ERROR;
    }

    for (n_buffers[index] = 0; n_buffers[index] < req.count; ++n_buffers[index]) {
        struct v4l2_buffer buf;

        CLEAR (buf);

        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = n_buffers[index];

        if (-1 == xioctl(fd[index], VIDIOC_QUERYBUF, &buf))
            return errnoexit("VIDIOC_QUERYBUF");

        buffers[index][n_buffers[index]].length = buf.length;
        buffers[index][n_buffers[index]].start =
                mmap(NULL,
                     buf.length,
                     PROT_READ | PROT_WRITE,
                     MAP_SHARED,
                     fd[index], buf.m.offset);

        if (MAP_FAILED == buffers[index][n_buffers[index]].start)
            return errnoexit("mmap");
    }

    return SUCCESS;
}

int startcapturing(int index) {
    unsigned int i;
    enum v4l2_buf_type type;

    for (i = 0; i < n_buffers[index]; ++i) {
        struct v4l2_buffer buf;

        CLEAR (buf);

        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;

        if (-1 == xioctl(fd[index], VIDIOC_QBUF, &buf)) {
            LOGE("in startcapturing");
            return errnoexit("VIDIOC_QBUF");
        }
    }

    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    if (-1 == xioctl(fd[index], VIDIOC_STREAMON, &type)) {
        LOGE("camid=%d", index);
        return errnoexit("VIDIOC_STREAMON");
    }

    return SUCCESS;
}

int uninitdevice(int index) {
    unsigned int i;
    for (i = 0; i < n_buffers[index]; ++i)
        if (-1 == munmap(buffers[index][i].start, buffers[index][i].length))
            return errnoexit("munmap");
    free(buffers[index]);
    return SUCCESS;
}

int closedevice(int index) {
    if (-1 == close(fd[index])) {
        fd[index] = -1;
        return errnoexit("close");
    }
    fd[index] = -1;
    return SUCCESS;
}

int stopcapturing(int index) {
    enum v4l2_buf_type type;
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (-1 == xioctl(fd[index], VIDIOC_STREAMOFF, &type))
        return errnoexit("VIDIOC_STREAMOFF");
    return SUCCESS;
}

int readframe(int i, char *buffer, int length) {
    struct v4l2_buffer buf;
    CLEAR (buf);
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    if (-1 == xioctl(fd[i], VIDIOC_DQBUF, &buf)) {
        switch (errno) {
            case EAGAIN:
                return 0;
            case EIO:
            default:
                return errnoexit("VIDIOC_DQBUF");
        }
    }
    assert (buf.index < n_buffers[i]);
//    processimage(i, buffers[i][buf.index].start, buffers[i][buf.index].length);

    int copyLength = 0;
    if (buffers[i][buf.index].length > length) {
        copyLength = length;
    } else {
        copyLength = buffers[i][buf.index].length;
    }

    memcpy(buffer, buffers[i][buf.index].start, copyLength);

    if (-1 == xioctl(fd[i], VIDIOC_QBUF, &buf)) {
        LOGE("in readframe");
        return errnoexit("VIDIOC_QBUF");
    }
    return copyLength;
}

int readframeonce(int i, char *buffer, int length) {
    int readLength = ERROR;
    for (;;) {
        fd_set fds;
        struct timeval tv;
        int r;

        FD_ZERO (&fds);
        FD_SET (fd[i], &fds);

        // Timeout
        tv.tv_sec = 2;
        tv.tv_usec = 0;

        r = select(fd[i] + 1, &fds, NULL, NULL, &tv);

        if (-1 == r) {
            if (EINTR == errno)
                continue;
            return errnoexit("select");
        }

        if (0 == r) {
            LOGE("select timeout");
            return ERROR;
        }
        readLength = readframe(i, buffer, length);
        if (readLength > 1)
            break;
    }

    return readLength;

}

int xioctl(int fd, int request, void *arg) {
    int r;

    do r = ioctl(fd, request, arg);
    while (-1 == r && EINTR == errno);

    return r;
}
